﻿using System.Collections.Specialized;
using Ags.MonsterTools.Common;
using Ags.MonsterTools.PingTool.Properties;
using Ags.Mvvm;
using Microsoft.Practices.Prism.Commands;
using System;
using System.ComponentModel;
using System.Diagnostics;
using System.Linq;
using System.Net;
using System.Threading;
using System.Threading.Tasks;

namespace Ags.MonsterTools.PingTool
{
    public class PingViewModel : ViewModelBase<PingViewModel>, IDataErrorInfo, IDisposable
    {
        private PingerProgress _progress = new PingerProgress(HandlePingReply);
        private static CancellationTokenSource _token;
        private static bool _stopPinger;
        private IStatusbarService _statusBar;
        private IMainWindowHandler _mwHandler;
        private const int ItemLimit = 1000;

        public PingViewModel(IStatusbarService statusBar, IMainWindowHandler mwHandler)
        {
            _statusBar = statusBar;
            _mwHandler = mwHandler;

            PingCommand = new DelegateCommand(ExecutePingCommand);
            StopCommand = new DelegateCommand(ExecuteStopCommand);
            ClearCommand = new DelegateCommand(ExecuteClearCommand);
            HideParentCommand = new DelegateCommand(ExecuteHideParentCommand);
            ShowParentCommand = new DelegateCommand(ExecuteShowParentCommand);
            
            _progress.ProgressChanged += _progress_ProgressChanged;
            _mwHandler.OnShown += _mwHandler_OnShown;
            
            LoadSettings();
            ResetToken();
            ActivateInputControls(true);
        }

        private void ResetToken()
        {
            _token = new CancellationTokenSource();
            _stopPinger = false;
        }

        private void LoadSettings()
        {
            this.Host = string.IsNullOrEmpty(Settings.Default.LastUrl) ? Settings.Default.DefaultHost : Settings.Default.LastUrl;
            this.Timeout = Settings.Default.DefaultTimeout;
            this.Delay = Settings.Default.DefaultInterval;
        }

        private LimitedObservableCollection<PingResult> _replyList;
        public LimitedObservableCollection<PingResult> ReplyList
        {
            get
            {
                if (_replyList == null)
                    _replyList = new LimitedObservableCollection<PingResult>(ItemLimit);
                return _replyList;
            }
            set
            {
                _replyList = value;
                OnPropertyChanged(m => m.ReplyList);
            }
        }

        private PingResult _lastReply;
        public PingResult LastReply
        {
            get { return _lastReply; }
            set
            {
                _lastReply = value;
                OnPropertyChanged(x => x.LastReply);
            }
        }

        private string _host;
        public string Host
        {
            get { return _host; }
            set
            {
                _host = value;
                OnPropertyChanged(m => m.Host);
            }
        }

        private string _msg;
        public string Msg
        {
            get { return _msg; }
            set
            {
                _msg = value;
                OnPropertyChanged(m => m.Msg);
            }
        }

        private int _delay;
        public int Delay
        {
            get { return _delay; }
            set
            {
                _delay = value;
                OnPropertyChanged(m => m.Delay);
            }
        }

        private int _timeout;
        public int Timeout
        {
            get { return _timeout; }
            set
            {
                _timeout = value;
                OnPropertyChanged(m => m.Timeout);
            }
        }

        private string _info;
        public string Info
        {
            get { return _info; }
            set
            {
                _info = value;
                OnPropertyChanged(m => m.Info);
            }
        }

        private bool _inputEnabled;
        public bool InputEnabled
        {
            get { return _inputEnabled; }
            set
            {
                _inputEnabled = value;
                OnPropertyChanged(m => m.InputEnabled);
            }
        }

        public DelegateCommand ClearCommand { get; private set; }
        void ExecuteClearCommand()
        {
            this.ReplyList.Clear();
            OnPropertyChanged(m => m.ReplyList);
            _statusBar.WriteMessage("Cleared");
        }

        public DelegateCommand StopCommand { get; private set; }
        void ExecuteStopCommand()
        {
            _stopPinger = true;
            _statusBar.WriteMessage("Stopping Ping...");
        }

        public DelegateCommand PingCommand { get; private set; }
        async void ExecutePingCommand()
        {
            ResetToken();
            ActivateInputControls(false);
            try
            {
                int timeOut = 0;
                timeOut = this.Timeout < 500 ? Settings.Default.DefaultTimeout : this.Timeout;
                int delay = 0;
                delay = this.Delay < 1000 ? Settings.Default.DefaultInterval : this.Delay;
                var host = await GetHost(this.Host);
                if (host.Any())
                {
                    SaveSettings();
                    _statusBar.WriteMessage("Ping started");
                    await Pinger.ContinuousPingAsync(host[0], timeOut, delay, _progress, _token.Token);
                }
                else
                {
                    _statusBar.WriteMessage("Could not resolve hostname");
                    Debug.WriteLine("Could not resolve hostname!");
                    ActivateInputControls(true);
                }
            }
            catch (Exception e)
            {
                _statusBar.WriteMessage("An error occured: " + e.Message);
                Debug.WriteLine(e);
                ActivateInputControls(true);
            }
        }

        private void SaveSettings()
        {
            Settings.Default.LastUrl = this.Host;
            Settings.Default.DefaultInterval = this.Delay;
            Settings.Default.DefaultTimeout = this.Timeout;
            Settings.Default.Save();
        }

        async Task<IPAddress[]> GetHost(string hostName)
        {
            return await Task<IPAddress[]>.Factory.StartNew(() =>
            {
                try
                {
                    if(string.IsNullOrEmpty(hostName))
                        return new IPAddress[0];
                    return Dns.GetHostAddresses(hostName);
                }
                catch (Exception)
                {
                    return new IPAddress[0];
                }
            });
        }

        void _progress_ProgressChanged(object sender, PingResult e)
        {
            this.ReplyList.Add(e);
            this.LastReply = e;
            if (_token != null && _token.Token.IsCancellationRequested)
            {
                _statusBar.WriteMessage("Ping stopped");
                ActivateInputControls(true);
            }
        }

        private void ActivateInputControls(bool p)
        {
            InputEnabled = p;
        }

        private static void HandlePingReply(PingResult reply)
        {
            Debug.WriteLine(reply.Result.ToPrettyString());
            if (_stopPinger)
            {
                _token.Cancel(false);
            }
        }

        public string Error
        {
            get { return null; }
        }

        public string this[string columnName]
        {
            get
            {
                string result = null;
                switch (columnName)
                {
                    case "Host":
                        if (string.IsNullOrEmpty(this.Host))
                            result = "Put in a hostname!";
                        break;
                }
                return result;
            }
        }

        public void Dispose()
        {
            this._statusBar = null;
            _progress.ProgressChanged -= _progress_ProgressChanged;
            this._progress = null;
            _token = null;
            _replyList.Clear();
            _replyList = null;
        }

        public DelegateCommand ShowParentCommand { get; private set; }

        void ExecuteShowParentCommand()
        {
            _mwHandler.Show();
        }


        public DelegateCommand HideParentCommand { get; private set; }

        void ExecuteHideParentCommand()
        {
            _mwHandler.Hide();
        }

        void _mwHandler_OnShown(object sender, EventArgs e)
        {
            this.ReplyList.RaiseCollectionChanged();
        }
    }
}
